Web scraping

La posibilidad de utilizar la información que está vertida en internet sin lugar a dudas abre una oportunidad inédita para diversos análisis que queramos hacer. R tiene un conjunto de funciones que nos permiten realizar esta tarea de una manera relativamente simple. En esta nota de clase vamos a tener una introducción a cómo scrapear contenido directamente desde la información vertida en la página web.

Cargando los paquetes

Para esta clase vamos a usar algunos paquetes bien clásicos como tidyverse, pero también vamos a introducirnos de lleno a rvest. Para lo que sigue, tenemos que tener cargadas estas librerías así que aprovechamos para hacerlo ahora. Recuerden que deben tener instalados estos paquetes, pueden hacerlo con la función install.packages()

library(tidyverse)
library(rvest)

Ahora sí, ya podemos empezar a pensar cómo scrapear una página web. El primer paso es comprender cómo está estructurada una página web. Hacía ahí vamos

Estructura de una página web

Cualquier página web, no importa qué tan compleja o como interactúe con el cliente, quien pide la información, y el servidor, donde se encuentran esos datos, tiene una estructura en cómun: Hyper Text Markup Language, más conocido como HTML. Para hacerlo fácil, HTML es un conjunto de etiquetas que definen los objetos (y sus atributos) que definen cómo se estructura la información en las páginas web que navegamos.

¿Por qué es importante entender esto? Por la sencilla razón que tenemos que conocer este “lenguaje” para poder comprender dónde se encuentra esa porción de la información que queremos almacenar. Empecemos por lo básico: abran Google Chrome - vamos a usar este navegador para las dos clases de scraping - y entren a la siguiente página de wikipedia: https://es.wikipedia.org/wiki/Anexo:Ganadores_del_Premio_Nobel

Una vez que lo haya cargado, vayan a los tres puntos verticales que tienen en la esquina superior derecha -> Más herramientas -> Herramientas del desarrollador (obviamente todo esto puede cambiar de nombre si lo tienen instalado en inglés). Pista: Pueden usar el atajo Ctrl+Shift+I de ahora en adelante. Van a encontrarse con un panel con muchas opciones… de a poco vamos a ir viendo qué nos pueden aportar cada una de ellas. Pueden usar el gif para guiarse

Cómo acceder a las herramientas de desarrollador en Google Chrome

Si colapsan <head> y <body> se van a encontrar con tres grandes “llaves”: <html>, <head> y <body>. Cada una de estas etiquetas definen el inicio de un objeto. En el caso de <html> indica el comienzo de una página, <head> contiene información muy importante como scripts de javascript o estilos de CSS, que prácticamente cuando estamos haciendo scraping no es necesario de investigar. Donde sucede toda la magia en nuestra tarea de scrapear es en <body> y todos los elementos que aparecen adentro de ella. Un detalle importante: cuando se introduce una etiqueta con una barra al principio, como en </body> indica que ese objeto termina en ese lugar.

El explorador de elementos de Google Chrome es muy inteligente. Prueben ir seleccionando los <div>, que se encuentran dentro de head, y van a ver que en la página que están navegando les va a aparecer coloreado en azul a que parte de la página estamos hace referencia. A la etiqueta <div> nos la vamos a encontrar en muchas situaciones, ya que funciona como “contenedor” de otro conjunto de objetos.

No es el objetivo de esta clase hacer un repaso exhaustivo de cada una de las etiquetas que existen en HTML, pero en https://www.w3schools.com/html/html_intro.asp tienen mucha información sobre cada una de ellas. Vayamos a lo importante: encontrar dónde está lo que queremos scrapear.

Identificando el elemento que contiene la información

¿Cómo podemos identificar exactamente en qué elemento está la información que queremos extraer? De nuevo una ayuda invaluable está en en simple navegador de Google Chrome. Imaginemos que queremos extraer el nombre del titulo de la página, eso que vemos en Anexo: Ganadores del Premio Nobel. Para extraerlo, tenemos que decirle a R dónde está esa información.

Hagan click secundario (o derecho) encima del título y elijan inspeccionar. Si no tenían abiertas las herramientas de desarrollador, se los va a abrir. Lo que va a hacer es posicionarnos exactamente en el elemento que contiene este texto. En este caso podemos ver el elemento es el siguiente:

<h1 id=“firstHeading” class=“firstHeading” lang=“es”>Anexo:Ganadores del Premio Nobel</h1>

Cómo identificar la etiqueta que contiene la información que queremos scrapear

El texto es lo que está entre la etiqueta <h1> y </h1>, que lo que indica son Headers, siendo h1 típicamente el de mayor importancia y tamaño. Importante: vean que dentro de la primera etiqueta nos indica también distintos atributos como id, class o lang. Los atributos son específicos a cada uno de estos elementos. id suele ser muy importante, ya que podemos identificar por nombre al elemento, mientras que la class hace referencia a la clase CSS, que es la que le da un estilo (por ejemplo, cierta tipografía, tamaño, etc, etc). lang es un atributo más extraño y es específico a la forma en la que desarrollaron la página de wikipedia.

Usando rvest para extraer información de las páginas

Ahora que ya identificamos donde está el titulo, veamos cómo le decimos a rvest que queremos el texto que se encuentra dentro de esa etiqueta:

read_html(x = "https://es.wikipedia.org/wiki/Anexo:Ganadores_del_Premio_Nobel") %>% 
  html_node(css = "h1") %>% 
  html_text()
## [1] "Anexo:Ganadores del Premio Nobel"

Funcionó, pero vamos a explicar bien qué es lo que hace cada parte

read_html(x = "https://es.wikipedia.org/wiki/Anexo:Ganadores_del_Premio_Nobel")
## {html_document}
## <html class="client-nojs" lang="es" dir="ltr">
## [1] <head>\n<meta http-equiv="Content-Type" content="text/html; charset=UTF-8 ...
## [2] <body class="mediawiki ltr sitedir-ltr mw-hide-empty-elt ns-104 ns-subjec ...

Les resulta familiar? Debería ! es la estructura de la página HTML. Lo que hace read_html() es descargar la página y convertirla a un documento XML, otra forma de representar estructura de datos con etiquetas. No es necesario que sepamos manejar documentos XML, ya que rvest nos permite seleccionar a diferentes elementos de la página mediante CSS selectors, lo que nos lleva a la segunda parte de nuestro pequeño código

La función html_node() nos devuelve los nodos (etiquetas) que seleccionamos según dos formas de elegirlos: CSS selector xpath. En esta clase vamos a ver CSS selectors, o al menos sus usos más comunes. Veamos: si escribimos h1 - o cualquier otro nombre de etiqueta - html_node() nos devolverá la primera vez que aparece esa etiqueta y html_nodes() todos los nodos con esa etiqueta. Ejecutemos solo esa parte

read_html(x = "https://es.wikipedia.org/wiki/Anexo:Ganadores_del_Premio_Nobel") %>% 
            html_node(css = "h1")
## {html_node}
## <h1 id="firstHeading" class="firstHeading" lang="es">

Como podemos ver, nos devolvió un nodo, pero sin indicar el texto que está dentro de se nodo. Ahora bien, usando # podemos encontrar el mismo nodo, pero mediante el valor que tiene el atributo id

read_html(x = "https://es.wikipedia.org/wiki/Anexo:Ganadores_del_Premio_Nobel") %>% 
            html_node(css = "#firstHeading")
## {html_node}
## <h1 id="firstHeading" class="firstHeading" lang="es">

Esto puede ser muy útil, porque muchas veces tenemos muchos objetos, pero solo uno se llama de esta manera. Sin embargo, no todas las páginas lo hacen tan fácil, y muchas veces tenemos que buscar por la clase, u otros atributos. Para la clase también hay un caracter reservado que es el . (un punto), presten atención al siguiente código:

read_html(x = "https://es.wikipedia.org/wiki/Anexo:Ganadores_del_Premio_Nobel") %>%
  html_node(css = ".firstHeading")
## {html_node}
## <h1 id="firstHeading" class="firstHeading" lang="es">

Devolvió el mismo elemento. Podemos generalizar todavía más esta forma de encontrar a las etiquetas por sus atributos de la siguiente manera

read_html(x = "https://es.wikipedia.org/wiki/Anexo:Ganadores_del_Premio_Nobel") %>%
  html_node(css = "h1[class=firstHeading]")
## {html_node}
## <h1 id="firstHeading" class="firstHeading" lang="es">
read_html(x = "https://es.wikipedia.org/wiki/Anexo:Ganadores_del_Premio_Nobel") %>%
  html_node(css = "h1[id=firstHeading]")
## {html_node}
## <h1 id="firstHeading" class="firstHeading" lang="es">

También estamos seleccionando exactamente al mismo elemento. La lógica es poner al elemento entre y luego “filtrar” según algún atributo que hayamos visto que tenía asociado y su valor. De esta manera podemos usar cualquier atributo que cualquiera de las páginas de internet use.

Nos falta la tercera línea de nuestro código:

Esta función de rvest lo único que hace es extraer el texto que se encuentra dentro del nodo o nodos que ya hayamos elegido anteriormente. No es lo único que podemos extraer de ellos, incluso podemos extraer valores de atributos o directamente la tabla como un data frame, como ya veremos en ejemplos más avanzados.

Su turno: extrayendo los idiomas en los que está disponible la página

Como ya deben todos saber, Wikipedia ofrece sus entradas sobre diversos temas en muchos idiomas ¿Pueden identificar donde aparece esta información? En lado izquierdo de la página. Encuentren un patrón en común entre estos links como para poder seleccionarlos y extraer su texto. Importante: van a tener que usarl html_nodes() en lugar de html_node(), ya que estamos queriendo seleccionar más de un nodo.

.

.

.

.

.

.

.

.

.

.

.

.

.

Una forma de resolverlo es la siguiente:

read_html("https://es.wikipedia.org/wiki/Anexo:Ganadores_del_Premio_Nobel") %>% 
  html_nodes(css = "a[class=interlanguage-link-target]") %>% 
  html_text()
##  [1] "<U+0627><U+0644><U+0639><U+0631><U+0628><U+064A><U+0629>" "Asturianu"          "Az<U+0259>rbaycanca"
##  [4] "<U+09AC><U+09BE><U+0982><U+09B2><U+09BE>" "Català"             "<U+06A9><U+0648><U+0631><U+062F><U+06CC>"
##  [7] "Dansk"              "Deutsch"            "<U+0921><U+094B><U+091F><U+0947><U+0932><U+0940>"
## [10] "English"            "<U+0641><U+0627><U+0631><U+0633><U+06CC>" "Suomi"             
## [13] "Français"           "Galego"             "<U+0A97><U+0AC1><U+0A9C><U+0AB0><U+0ABE><U+0AA4><U+0AC0>"
## [16] "<U+0939><U+093F><U+0928><U+094D><U+0926><U+0940>" "Magyar"             "Bahasa Indonesia"  
## [19] "<U+65E5><U+672C><U+8A9E>" "Jawa"               "<U+10E5><U+10D0><U+10E0><U+10D7><U+10E3><U+10DA><U+10D8>"
## [22] "<U+D55C><U+AD6D><U+C5B4>" "Kurdî"              "Lingua Franca Nova"
## [25] "Latviešu"           "<U+0D2E><U+0D32><U+0D2F><U+0D3E><U+0D33><U+0D02>" "<U+041C><U+043E><U+043D><U+0433><U+043E><U+043B>"
## [28] "Bahasa Melayu"      "<U+0928><U+0947><U+092A><U+093E><U+0932><U+0940>" "Norsk bokmål"      
## [31] "<U+0A2A><U+0A70><U+0A1C><U+0A3E><U+0A2C><U+0A40>" "Polski"             "<U+067E><U+0646><U+062C><U+0627><U+0628><U+06CC>"
## [34] "Português"          "Româna"             "<U+0420><U+0443><U+0441><U+0441><U+043A><U+0438><U+0439>"
## [37] "<U+0938><U+0902><U+0938><U+094D><U+0915><U+0943><U+0924><U+092E><U+094D>" "Scots"              "Slovenšcina"       
## [40] "Shqip"              "<U+0421><U+0440><U+043F><U+0441><U+043A><U+0438> / srpski" "Svenska"           
## [43] "<U+0BA4><U+0BAE><U+0BBF><U+0BB4><U+0BCD>" "<U+0C24><U+0C46><U+0C32><U+0C41><U+0C17><U+0C41>" "Türkçe"            
## [46] "<U+0423><U+043A><U+0440><U+0430><U+0457><U+043D><U+0441><U+044C><U+043A><U+0430>" "<U+0627><U+0631><U+062F><U+0648>" "Ti<U+1EBF>ng Vi<U+1EC7>t"
## [49] "Yorùbá"             "<U+4E2D><U+6587>"